using System;
using System.Web.Caching;
using BoxBinary.AspectCache.Core.CacheManagement;
using BoxBinary.AspectCache.Core.ConfigurationMappers;

namespace BoxBinary.AspectCache.Core.PolicyDefinition
{
    /// <summary>
    /// A policy describing the cacheability of an object, inferred from the <see cref="CacheItemFootprint"/> flags specified in the given <see cref="CachePolicySpecification"/>.
    /// </summary>
    [Serializable]
    public class PolicyBoundCachePackage : IPolicyBoundCachePackage, ICloneable
    {
        #region Constructors

        protected PolicyBoundCachePackage()
        {
        }

        /// <summary>
        /// Constructor for initialising members based on an <see cref="ICacheable"/> object, which guarantees the specification of
        /// a cache category key.
        /// </summary>
        /// <param name="objectToCache"></param>
        public PolicyBoundCachePackage(ICacheable objectToCache)
            : this(objectToCache, Configuration.Current.CachePolicySpecifications[objectToCache.CachePolicy].Footprint)
        {
        }

        /// <summary>
        /// Populates the <see cref="PolicyBoundCachePackage"/> object based on <see cref="CacheItemFootprint"/> flags which will allow a policy to be inferred
        /// about whether items are cached to disk or RAM, for how long, and at what priority.
        /// </summary>
        /// <param name="objectToCache"></param>
        /// <param name="footprintFlags"></param>
        public PolicyBoundCachePackage(ICacheable objectToCache, CacheItemFootprint footprintFlags)
        {
            if (objectToCache.GetType() == typeof (CacheItemWrapper))
                Item = ((CacheItemWrapper) objectToCache).Item;
            else
                Item = objectToCache;

            Key = CacheKeyBuilder.GetKey(objectToCache);
            // We use AbsoluteExpiration rather than Sliding Expiration because
            // we're deciding a policy here, about how long an item should be cached
            // based on how often our data changes, or how expensive an item is to instantiate
            // - not about how the internal Cache management should behave with regards
            // to its memory management. If we set a SlidingExpiration, if an object
            // was accessed often, it would never expire and therefore our data 
            // wouldn't show as refreshed on the site.
            AbsoluteExpiration = DateTime.Now.AddHours(1);
            Priority = CacheItemPriority.AboveNormal;
            _cacheStoreMechanism = CacheStoreMechanism.Default;
            ItemCachedDateStamp = DateTime.Now;

            // ANalyse the footprint flags - from lowest order to highest, so that any
            // high-order flags override cache-sticky values

            if (IsFlagPresent(footprintFlags, CacheItemFootprint.NilChangeFrequency))
            {
                AbsoluteExpiration = Cache.NoAbsoluteExpiration;
                Priority = CacheItemPriority.NotRemovable;
            }

            if (IsFlagPresent(footprintFlags, CacheItemFootprint.LowChangeFrequency))
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(Configuration.Current.PolicyGovernanceDefaults.MaxCacheMinutes);

                // If item is quick to create, or hungry to store, specify that it should
                // be removed first from the cache when freeing memory. Note that by default
                // we are specifying AboveNormal priority.
                if (
                    IsFlagPresent(footprintFlags, CacheItemFootprint.LowCreationExpense,
                                  CacheItemFootprint.HighHeapRequirement))
                    Priority = CacheItemPriority.Normal;

                // If item is slow to create or uses low ram, cache it at a higher priority than others
                if (
                    IsFlagPresent(footprintFlags, CacheItemFootprint.HighCreationExpense,
                                  CacheItemFootprint.LowHeapRequirement))
                    Priority = CacheItemPriority.High;

                if (
                    IsFlagPresent(footprintFlags, CacheItemFootprint.MediumHeapRequirement,
                                  CacheItemFootprint.HighHeapRequirement) &&
                    IsFlagPresent(footprintFlags, CacheItemFootprint.HighCreationExpense))
                {
                    _cacheStoreMechanism = CacheStoreMechanism.PersistenceProvider;
                    Priority = CacheItemPriority.NotRemovable;
                }
            }

            if (IsFlagPresent(footprintFlags, CacheItemFootprint.MediumChangeFrequency))
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(Configuration.Current.PolicyGovernanceDefaults.MedCacheMinutes);

                // If item is moderately or highly slow to create, and has a low memory footprint, cache to disk
                if (
                    IsFlagPresent(footprintFlags, CacheItemFootprint.MediumCreationExpense,
                                  CacheItemFootprint.HighCreationExpense) &&
                    IsFlagPresent(footprintFlags, CacheItemFootprint.LowHeapRequirement))
                {
                    _cacheStoreMechanism = CacheStoreMechanism.PersistenceProvider;
                    Priority = CacheItemPriority.NotRemovable;
                }
            }

            if (IsFlagPresent(footprintFlags, CacheItemFootprint.HighChangeFrequency))
            {
                // Cache min time
                AbsoluteExpiration = DateTime.Now.AddMinutes(Configuration.Current.PolicyGovernanceDefaults.MinCacheMinutes);

                // If item is moderately or highly slow to create, and has a low memory footprint, cache to disk
                if (
                    IsFlagPresent(footprintFlags, CacheItemFootprint.MediumCreationExpense,
                                  CacheItemFootprint.HighCreationExpense) &&
                    IsFlagPresent(footprintFlags, CacheItemFootprint.LowHeapRequirement))
                {
                    _cacheStoreMechanism = CacheStoreMechanism.PersistenceProvider;
                    Priority = CacheItemPriority.NotRemovable;
                }
            }

            if (IsFlagPresent(footprintFlags, CacheItemFootprint.ForceSurviveApplicationRestarts))
            {
                Priority = CacheItemPriority.NotRemovable;
                _cacheStoreMechanism = CacheStoreMechanism.PersistenceProvider;
                ShouldSurviveApplicationRestarts = true;
            }
        }

        /// <summary>
        /// Performs a bitwise AND operation on a set of <see cref="CacheItemFootprint"/> flags to determine a particular flags
        /// presence. Each <see cref="CacheItemFootprint"/> in <paramref name="findFlags"/> will be searched for in 
        /// <paramref name="searchFlags"/> and, if present, will return true.
        /// </summary>
        private bool IsFlagPresent(CacheItemFootprint searchFlags, params CacheItemFootprint[] findFlags)
        {
            foreach (CacheItemFootprint itemFootprint in findFlags)
            {
                if ((searchFlags & itemFootprint) == itemFootprint)
                    return true;
            }
            return false;
        }

        #endregion

        #region Private instance variables

        private CacheStoreMechanism _cacheStoreMechanism = CacheStoreMechanism.Default;

        #endregion

        #region Properties

        private DateTime _absoluteExpiration;
        private object _item;
        private DateTime _itemCachedDateStamp;
        private string _key;
        private CacheItemPriority _priority;
        private bool _shouldSurviveApplicationRestarts;

        /// <summary>
        /// Whether the cache entry should be persisted to disk in such a way as to survive
        /// the recycling of the AppDomain running the current application.
        /// </summary>
        public bool ShouldSurviveApplicationRestarts
        {
            get { return _shouldSurviveApplicationRestarts; }
            set { _shouldSurviveApplicationRestarts = value; }
        }

        /// <summary>
        /// The cache entry key.
        /// </summary>
        public string Key
        {
            set { _key = value; }
            get { return _key; }
        }

        /// <summary>
        /// The object to be cached.
        /// </summary>
        public object Item
        {
            set { _item = value; }
            get { return _item; }
        }

        /// <summary>
        /// The absolute expiration date and time of the cache item.
        /// </summary>
        public DateTime AbsoluteExpiration
        {
            set { _absoluteExpiration = value; }
            get { return _absoluteExpiration; }
        }

        /// <summary>
        /// Specifies the relative priority of items stored in the <see cref="T:System.Web.Caching.Cache"></see> object.
        /// </summary>
        public CacheItemPriority Priority
        {
            set { _priority = value; }
            get { return _priority; }
        }

        /// <summary>
        /// The path on which the cache entry depends. If this path changes, the cache entry is invalidated.
        /// </summary>
        public string CacheDependencyPath { set; get; }

        /// <summary>
        /// The mechanism for storing the cache entry.
        /// </summary>
        public CacheStoreMechanism CacheStore
        {
            get { return _cacheStoreMechanism; }
            set { _cacheStoreMechanism = value; }
        }

        /// <summary>
        /// DateTime that the item was first cached.
        /// </summary>
        public DateTime ItemCachedDateStamp
        {
            get { return _itemCachedDateStamp; }
            set { _itemCachedDateStamp = value; }
        }

        #endregion

        #region ICloneable Members

        /// <summary>
        /// An explicit interface method implementation, available
        /// for clients of <see cref="ICloneable"/>, but invisible to 
        /// casual clients of <see cref="PolicyBoundCachePackage"/>.
        /// </summary>
        /// <returns></returns>
        object ICloneable.Clone()
        {
            // simply delegate to our type-safe cousin
            return Clone();
        }

        #endregion

        /// <summary>
        /// A strongly typed Clone implementation. Returns a shallow clone of the current cache entry together 
        /// with a shallow clone of the value of <see cref="Item"/>.
        /// </summary>
        /// <returns></returns>
        public virtual PolicyBoundCachePackage Clone()
        {
            // Start with a flat, memberwise copy
            var clone = (PolicyBoundCachePackage) MemberwiseClone();

            // Then deep-copy everything that needs the 
            // special attention
            if (Item is ICloneable)
                clone.Item = ((ICloneable) Item).Clone();

            return clone;
        }
    }
}